home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PCMania 73
/
PCMania CD73_1.iso
/
sharewar
/
utiles
/
viff
/
viff.c
< prev
next >
Wrap
C/C++ Source or Header
|
1998-08-27
|
55KB
|
2,076 lines
/************************* -*- Mode: C -*- *****************************
*
* viff.c -- visual diff
*
* Copyright (C) 1994-1998 Richard Flamsholt S0rensen
*
* Author : Richard Flamsholt S0rensen
* Created On : Thu Jan 06 21:20:24 1994
* Last Modified By: Richard Flamsholt S0rensen
* Last Modified On: Thu Aug 27 13:40:22 1998
* Update Count : 1259
* Revision History: None
*
* TODO
* goto-local in viffmode shall pick current if that's "selected"
* better sideways scroll
* viff-highlighting (eg red background) of differences in curr diff
* make browse-list for backwards/forwards
* unix: fork+freopen+exec, so eg diff process can be interrupted
* goto next/prev modification
* pg up/down: increment scrolllines, starting with just a few lines
* implement more emacs commands (mark region, kill/yank region)
**********************************************************************/
#include <stdio.h>
#include <time.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include "viff.h"
/* requires a compiler with stat() call */
#include <sys/types.h>
#include <sys/stat.h>
#ifdef UNIX
#include <fcntl.h>
#include <unistd.h>
#endif
#ifdef MSDOS
#include "unixutil.h"
#endif
#ifdef BC31
extern unsigned _stklen = 8000;
#endif
#define WRITE_ALL 0x01
#define EXISTING_FILE 0x02
#define DOS_EOL "\x0d\x0a"
#define UNIX_EOL "\x0a"
#ifdef UNIX
#define VIFFEXT ".viff"
#define CONTSTR "..."
#define SPACE_CHAR '_'
#define TAB_CHAR '_'
#define CONT_CHAR '>'
#define CTRL_CHAR '?'
#define FRAME_TOPCH 'v'
#define FRAME_TOPLEFT FRAME_TOPCH
#define FRAME_TOPRIGHT FRAME_TOPCH
#define FRAME_MIDCH '-'
#define FRAME_MIDLEFT FRAME_MIDCH
#define FRAME_MIDRIGHT FRAME_MIDCH
#define FRAME_BOTCH '^'
#define FRAME_BOTLEFT FRAME_BOTCH
#define FRAME_BOTRIGHT FRAME_BOTCH
#define FRAME_NAMELEFT '('
#define FRAME_NAMERIGHT ')'
#define ISCTRL(ch) ((ch)!='\t' && ((unsigned char)((ch)&0x7f)<0x20 || (ch)==0x7f))
#define ADDRAWCH(ch) (void)addch(ch)
#define EOL UNIX_EOL
#else
#define VIFFEXT ".vif" /* default ext for layout files */
#define CONTSTR "\xfa\xfa\xfa" /* if filenames are too long */
#define SPACE_CHAR 0x07 /* small bullet */
#define TAB_CHAR 0x04 /* diamond shape */
#define CONT_CHAR 0x1a /* small right arrow */
#define CTRL_CHAR 0xf9 /* middle sized dot */
#define FRAME_TOPCH (unsigned char)196
#define FRAME_TOPLEFT (unsigned char)218
#define FRAME_TOPRIGHT (unsigned char)191
#define FRAME_MIDCH '-'
#define FRAME_MIDLEFT ' '
#define FRAME_MIDRIGHT ' '
#define FRAME_BOTCH (unsigned char)196
#define FRAME_BOTLEFT (unsigned char)192
#define FRAME_BOTRIGHT (unsigned char)217
#define FRAME_NAMELEFT (unsigned char)180
#define FRAME_NAMERIGHT (unsigned char)195
#define ISCTRL(ch) ((ch)!='\t' && ((ch)<0x20 || (ch)==0x7f))
#define ADDRAWCH(ch) (void)addrawch(ch)
#define EOL DOS_EOL
#endif
#define CTRLCH_COLORID 1
#define CTRLCH_FGCOLOR COLOR_CYAN
#define CTRLCH_BGCOLOR COLOR_BLACK
#define CTRLCH_ATTR 0 /* becomes A_BOLD when line is selected */
static chtype ctrlch_attr=0;
#define SPACE_COLORID 2
#define SPACE_FGCOLOR COLOR_CYAN
#define SPACE_BGCOLOR COLOR_BLACK
#define SPACE_ATTR 0 /* becomes A_BOLD when line is selected */
static chtype space_attr=0;
#define CONT_COLORID 3
#define CONT_FGCOLOR COLOR_MAGENTA
#define CONT_BGCOLOR COLOR_BLACK
#define CONT_ATTR 0 /* becomes A_BOLD when line is selected */
static chtype cont_attr=0;
#define SYSL_COLORID 4
#define SYSL_FGCOLOR COLOR_CYAN
#define SYSL_BGCOLOR COLOR_BLACK
#define SYSL_ATTR 0
static chtype sysl_attr=0;
#define HELP_COLORID 5
#define HELP_FGCOLOR COLOR_YELLOW
#define HELP_BGCOLOR COLOR_BLUE
#define HELP_ATTR A_BOLD
static chtype help_attr=0;
#define INFO_COLORID 6
#define INFO_FGCOLOR COLOR_YELLOW
#define INFO_BGCOLOR COLOR_RED
#define INFO_ATTR A_BOLD
static chtype info_attr=0;
#define FILE_COLORID 7
#define FILE_FGCOLOR COLOR_WHITE
#define FILE_BGCOLOR COLOR_RED
#define FILE_ATTR A_BOLD
static chtype file_attr=0;
#define VIFF_COLORID 8
#define VIFF_FGCOLOR COLOR_WHITE
#define VIFF_BGCOLOR COLOR_GREEN
#define VIFF_ATTR A_BOLD
chtype viff_attr=0;
#define EDIT_COLORID 9
#define EDIT_FGCOLOR COLOR_WHITE
#define EDIT_BGCOLOR COLOR_RED
#define EDIT_ATTR A_BOLD
chtype edit_attr=0;
chtype status_attr;
#define MAXLINELEN 2000
#define LINETABINC 1000
#define VIFFNAME_LEN 30
#define MAXFRAMEWIDTH 240
#define ATTR_COMMON A_BOLD
#define ATTR_INCL A_BOLD
#define ATTR_EXCL 0
#define MAXTABWIDTH 32
static char *viffmode_help[] = {
" Viff "VERSION,
" by Richard Flamsholt",
"",
"previous diff: C-p up select file 1 text: C-b left",
"next diff: C-n down select file 2 text: C-f right",
"first diff: C-a home toggle selected: space",
"last diff: C-e end select all of 1: (",
"move in 1/2/all: tab select all of 2: )",
" collapse the diff: +",
"",
"move view up: M-v pgup save to file 1: 1",
"move view down: C-v pgdn save to file 2: 2",
"focus on diff: . save to both files: 3",
"goto local diff C-l save to new file: 4",
"search forward: C-s save with layout: 5",
"search backward: C-r",
"edit text: C-o ins goto previous file: <",
" goto next file: >",
"set tabwidth: t show file info: i",
#ifdef UNIX
"redraw screen: r",
#else
"",
#endif
" quit: q esc (Q will quit all)",
""
};
static char *quitmodified_help[] = {
"The text has been modified and the",
"changed files have not been saved.",
"",
" yes, quit without saving: y",
" no, do not quit: n esc",
"",
" save into file1 and quit: 1",
" save into file2 and quit: 2",
" save into both files and quit: 3",
" save into new file and quit: 4",
" save layout into file and quit: 5",
};
static char *saveoverwrite_help[] = {
"A file by that name already exist.",
"Do you wish to overwrite that file?",
"",
" yes, overwrite the file: y",
" no, cancel: n"
};
static char *stop_help[] = {
"You have asked viff to quit.",
"Do you really want to quit?",
"",
" yes, quit viff: y",
" no, continue: n"
};
static char *header_table[] = {
"$Header:",
"$RCSfile:",
NULL
};
typedef enum {
FOCUS_FILE1, FOCUS_FILE2, FOCUS_BOTH
} FOCUS;
static FOCUS Focus = FOCUS_BOTH;
typedef struct {
int beg1, end1;
int beg2, end2;
char cmd;
} EDITCMD;
int topln;
int diffbeg, diffmid, diffend;
LINE *linetbl;
int nline = 0;
int linetblsiz = 0;
int tabwidth = 8;
BOOLEAN modified = FALSE;
int Scrollx = 0;
BOOLEAN curses_on = FALSE;
char *diffcmd = NULL;
char *options = NULL;
BOOLEAN verbose = FALSE;
BOOLEAN report = FALSE;
BOOLEAN brief_report = FALSE;
BOOLEAN ignoreheaders = FALSE;
BOOLEAN monochrome = FALSE;
static int ndiff, currdiff, sel1;
static char *Fname1, *Fname2;
static char *Eol1, *Eol2;
static int Nfiles, Fileno;
static int Ndiff = 0;
static int Ndiffrep = 0;
static char toptext[MAXFRAMEWIDTH+1];
static char midtext[MAXFRAMEWIDTH+1];
static char bottext[MAXFRAMEWIDTH+1];
static char viffname[VIFFNAME_LEN+1];
static BOOLEAN input_filename(char *buf, int ch);
static void version_info(void);
static void file_info(void);
static void file_info_do(WINDOW *win, struct stat *st, int y, int width, char *title, char *fname);
static void ffile_info(FILE *fp);
static void ffile_info_do(FILE *fp, const char *title, char *fname);
static chtype init_color_do(short id, short fg, short bg, chtype attr);
static void init_curses(void);
static void cleanup(void);
static void cleanup_curses(void);
static void free_difflines(void);
static void viff_statusline(void);
static void fstatusline(char *mode, char *fmt, ...);
static void build_text(char *buf, char left, char ch, char right, char *fname);
static void build_frame_texts(void);
static void skip_lines(FILE *fp, int n);
static void buffer_text_line(LINETYPE type, BOOLEAN incl, char *text, int line1, int line2);
static LINE *buffer_line(LINETYPE type, BOOLEAN incl);
static void putline_txt(LINE *line);
static char *read_num(char *str, int *val);
static BOOLEAN read_edit_cmd(FILE *fp, EDITCMD *cmd, BOOLEAN *errmsg);
static int user_commands(void);
static BOOLEAN viff_files(int *chp);
static int first_diff_to_show(void);
static BOOLEAN is_header_line(LINE *line, char *header);
static FILE *open_viff_file(char *fname, char **eol, int *chp);
static void collapse_diff(void);
static void choose_diff(BOOLEAN diff1);
static void choose_all_diff(BOOLEAN diff1);
static BOOLEAN write_file(char *fname, char *eol, int flags);
static void goto_diff(int diff);
static void set_curr_diff(int midln, int diff);
static void goto_first_diff(void);
static void goto_last_diff(void);
static BOOLEAN goto_next_diff();
static BOOLEAN goto_prev_diff();
static void set_tab_width();
static void set_focus();
static void check_for_stop(void);
BOOLEAN
confirm_quit(void)
{
int ch;
if (!modified && sel1 == ndiff) return TRUE;
ch = ask(quitmodified_help, ARRAYSIZE(quitmodified_help),
"yn12345", 'n', "%s%s%s -- really cancel?",
sel1<ndiff ? "selections made" : "",
sel1<ndiff && modified ? " and " : "",
modified ? "text modified" : "");
switch (ch) {
case 'y': return TRUE;
case 'n': return FALSE;
case '1': return write_file(Fname1, Eol1, EXISTING_FILE);
case '2': return write_file(Fname2, Eol2, EXISTING_FILE);
case '3': return (write_file(Fname1, Eol1, EXISTING_FILE) &&
write_file(Fname2, Eol2, EXISTING_FILE));
case '4':
case '5':
{ char buf[300];
if (!input_filename(buf, ch)) return FALSE;
return write_file(buf, EOL, ch=='5'?WRITE_ALL:0);
}
}
/* we should never come here */
return FALSE;
}
static BOOLEAN
input_filename(char *buf, int ch)
{
struct stat st;
char *save;
char *p;
strcpy(buf, Fname1);
p = strip_path(buf);
if (ch == '4') { /* save text; suggest Fname1's path */
save = "text";
*p = '\0';
} else { /* save layout; suggest Fname1.vif */
save = "layout";
#ifdef MSDOS
p = strchr(p, '.'); /* find the extension, if any */
if (p) strcpy(p, VIFFEXT);
else
#endif
strcat(buf, VIFFEXT);
}
return (input(0, buf, "enter filename (saving %s):", save) &&
(stat(buf, &st) != 0 ||
ask(saveoverwrite_help, ARRAYSIZE(saveoverwrite_help),
"yn", 'n', "file \"%s\" exist - overwrite it?", buf) == 'y'));
}
void
statusline(unsigned flags, const char *fmt, ...)
{
va_list varg;
va_start(varg, fmt);
vstatusline(flags, fmt, varg);
va_end(varg);
}
int
vstatusline(unsigned flags, const char *fmt, va_list varg)
{
char buf[300];
int len, i;
vsprintf(buf, fmt, varg);
(void)attron(status_attr);
(void)move(VIEWLINES, 0);
(void)clrtoeol();
(void)addch(' ');
(void)addstr(buf);
len = strlen(buf)+1;
for (i = len; i < COLS; i++) {
(void)addch(' ');
}
buf[0] = '\0';
if (flags & STATUS_RATING && ndiff >= 300) {
char *rating;
if (ndiff < 400) rating = "hard";
else if (ndiff < 600) rating = "tough";
else if (ndiff == 666) rating = "beast";
else if (ndiff < 800) rating = "nasty";
else if (ndiff < 1000) rating = "monster";
else if (ndiff == 1001) rating = "svedig";
else if (ndiff < 1500) rating = "killer";
else if (ndiff < 2000) rating = "evil";
else if (ndiff < 2500) rating = "deathly";
else rating = "argh!";
sprintf(buf, "(%s) ", rating);
}
if (flags & STATUS_HELP) {
strcat(buf, "F1=help");
}
if (buf[0] != '\0') {
(void)move(VIEWLINES, COLS-1 - strlen(buf));
(void)addstr(buf);
}
(void)standend();
return len;
}
static void
fstatusline(char *mode, char *fmt, ...)
{
char status[100];
va_list varg;
va_start(varg, fmt);
vsprintf(status, fmt, varg);
va_end(varg);
statusline(STATUS_HELP|STATUS_RATING,
" %s %s %s %s",
mode, modified ? "**" : " ",
viffname, status);
}
static void
version_info(void)
{
printf("Viff " VERSION "\n");
printf("Copyright 1994-1998 Richard Flamsholt\n");
printf("Compiled %s (%s %s)\n", viffdate, PLATFORM, COMPILER);
}
#define FRAME 1
#define XINDENT 1
#define XEXTRA (FRAME+XINDENT+XINDENT+FRAME)
#define YEXTRA (FRAME+FRAME)
void
help(char *text[], int nlines)
{
WINDOW *win;
int width;
int i;
for (i=0, width=0; i < nlines; i++) {
int len = strlen(text[i]);
if (width < len) width = len;
}
/* handle too small screens */
if (nlines > LINES-XEXTRA) nlines = LINES-XEXTRA;
if (width > COLS-YEXTRA) width = COLS-YEXTRA;
/* newwin(height,width,top,left) */
win = newwin(nlines+YEXTRA,
width+XEXTRA,
(LINES-(nlines+YEXTRA))/3, /* place 1/3 from top of screen */
(COLS-(width+XEXTRA))/2); /* place in the middle of screen */
if (win == (WINDOW*)NULL) {
message("could not create help window");
return;
}
(void)wattron(win, help_attr);
(void)wbkgd(win, COLOR_PAIR(HELP_COLORID));
(void)werase(win);
(void)box(win, 0, 0);
for (i = 0; i < nlines; i++) {
(void)mvwaddstr(win, i+FRAME, FRAME+XINDENT, text[i]);
}
(void)wmove(win, 0, 0);
(void)wrefresh(win);
(void)getch();
(void)delwin(win);
(void)touchwin(stdscr);
(void)refresh();
}
static void
file_info(void)
{
WINDOW *win;
char buf[100];
char title[50];
struct stat st1, st2;
int width, height;
int ypos;
long dt;
strcpy(title, "Info");
if (Nfiles > 1) {
sprintf(title+strlen(title), " (%d of %d)", Fileno, Nfiles);
}
width = MAX(strlen(Fname1), strlen(Fname2)) + 1 +2+6+2;
width = MAX((int)strlen(title) + 2, width);
width = MAX(20+1 + 2+6+2, MIN(width, COLS-4));
height = 16;
win = newwin(height, width, (LINES-height)/3, (COLS-width)/2);
if (win == (WINDOW*)NULL) {
message("could not create info window");
return;
}
(void)wattron(win, info_attr);
(void)wbkgd(win, COLOR_PAIR(INFO_COLORID));
(void)werase(win);
(void)box(win, 0, 0);
ypos = 1;
(void)mvwaddstr(win, ypos++, (width-(int)strlen(title))/2, title);
ypos++;
file_info_do(win, &st1, ypos, width, "File 1", Fname1);
file_info_do(win, &st2, ypos+7, width, "File 2", Fname2);
(void)wattroff(win, info_attr); (void)wattron(win, file_attr);
if (st1.st_size == st2.st_size) {
strcpy(buf, "same size");
} else {
long dsize = labs(st1.st_size - st2.st_size);
sprintf(buf, "%ld byte%s %s",
dsize, dsize==1?"":"s",
st1.st_size < st2.st_size ? "smaller" : "bigger");
}
(void)mvwaddstr(win, ypos+4, 3, buf);
dt = labs(st1.st_mtime - st2.st_mtime);
if (dt == 0) {
strcpy(buf, "same age");
} else {
if (dt < 60) { /* less than a minute */
sprintf(buf, "%ld second%s", dt, dt==1?"":"s");
} else if ((dt/=60) < 60) { /* less than an hour */
sprintf(buf, "%ld minute%s", dt, dt==1?"":"s");
} else if ((dt/=60) < 24) { /* less than a day */
sprintf(buf, "%ld hour%s", dt, dt==1?"":"s");
} else if ((dt/=24) < 2*30) { /* less than two months */
sprintf(buf, "%ld day%s", dt, dt==1?"":"s");
} else if (dt < 366) { /* less than a year */
dt /= 30; /* 30 is approx one month */
sprintf(buf, "%ld month%s", dt, dt==1?"":"s");
} else { /* more than a year */
dt /= 366;
sprintf(buf, "%ld year%s", dt, dt==1?"":"s");
}
strcat(buf, st1.st_mtime < st2.st_mtime ? " older" : " newer");
}
(void)mvwaddstr(win, ypos+5, 3, buf);
(void)wattroff(win, file_attr); (void)wattron(win, info_attr);
(void)wmove(win, 0, 0);
(void)wrefresh(win);
(void)getch();
(void)delwin(win);
(void)touchwin(stdscr);
(void)refresh();
}
static void
file_info_do(WINDOW *win, struct stat *st, int y, int width, char *title, char *fname)
{
char cont[10];
int xpos;
xpos = 2;
(void)mvwaddstr(win, y+0, xpos, title);
(void)mvwaddstr(win, y+1, xpos, "Name:");
(void)mvwaddstr(win, y+2, xpos, "Date:");
(void)mvwaddstr(win, y+3, xpos, "Size:");
xpos += 6;
(void)wattroff(win, info_attr); (void)wattron(win, file_attr);
if (stat(fname, st) != 0) {
(void)mvwaddstr(win, y+2, xpos, "?");
(void)mvwaddstr(win, y+3, xpos, "?");
} else {
char buf[100];
strftime(buf, sizeof buf, "%a %b %d %H:%M %Y",
localtime((time_t*)&st->st_mtime));
(void)mvwaddstr(win, y+2, xpos, buf);
sprintf(buf, "%ld byte%s", (long)st->st_size, st->st_size==1?"":"s");
(void)mvwaddstr(win, y+3, xpos, buf);
}
width -= 1+2+6+2;
cont[0] = '\0';
if ((int)strlen(fname) > width) {
#if defined(MSDOS) || defined(WIN32)
if (isalpha(((unsigned char*)fname)[0]) && fname[1] == ':') {
memcpy(cont, fname, fname[2]==SEPCHAR ? 3 : 2);
}
#endif
strcat(cont, CONTSTR);
fname += strlen(cont)+strlen(fname)-width;
}
(void)mvwaddstr(win, y+1, xpos, cont);
(void)mvwaddstr(win, y+1, xpos+strlen(cont), fname);
(void)wattroff(win, file_attr); (void)wattron(win, info_attr);
}
static void
ffile_info(FILE *fp)
{
time_t t = time((time_t*)NULL);
char *tim = ctime(&t);
tim[strlen(tim)-1] = '\0'; /* remove that stupid trailing \n */
fprintf(fp, EOL "%*sViff Report" EOL, 34, "");
fprintf(fp, EOL "%*s%s" EOL EOL, 27, "", tim);
ffile_info_do(fp, "File 1", Fname1);
ffile_info_do(fp, "File 2", Fname2);
fprintf(fp, EOL "%d difference%s" EOL, ndiff, ndiff==1?"":"s");
fprintf(fp, EOL "[***end of viff header***]" EOL);
}
static void
ffile_info_do(FILE *fp, const char *title, char *fname)
{
struct stat st;
fprintf(fp, "%s:" EOL " Name: %s" EOL, title, fname);
if (stat(fname, &st) == 0) {
char buf[100];
strftime(buf, sizeof buf, "%a %d %b %H:%M",
localtime((time_t*)&st.st_mtime));
fprintf(fp, " Date: %s" EOL, buf);
fprintf(fp, " Size: %ld byte%s" EOL, (long)st.st_size, st.st_size==1?"":"s");
}
}
static chtype
init_color_do(short id, short fg, short bg, chtype attr)
{
if (init_pair(id, fg, bg) == ERR) {
error("failed to initialize color %d", id);
}
return attr|COLOR_PAIR(id);
}
static void
init_curses(void)
{
if (report) return; /* no init nor cleanup for reports */
#ifdef UNIX
if (!isatty(1)) {
close(1);
if (open("/dev/tty", O_WRONLY) == -1) { /* reopen stdout to terminal */
error("you cannot pipe viff's output: %s", my_strerror());
}
}
#endif
if (initscr() == (WINDOW*)NULL) {
panic("curses: initscr() failed");
}
atexit(cleanup_curses);
curses_on = TRUE;
if (!monochrome && has_colors() && start_color()==OK) {
ctrlch_attr = init_color_do(CTRLCH_COLORID, CTRLCH_FGCOLOR, CTRLCH_BGCOLOR, CTRLCH_ATTR);
space_attr = init_color_do(SPACE_COLORID, SPACE_FGCOLOR, SPACE_BGCOLOR, SPACE_ATTR);
cont_attr = init_color_do(CONT_COLORID, CONT_FGCOLOR, CONT_BGCOLOR, CONT_ATTR);
sysl_attr = init_color_do(SYSL_COLORID, SYSL_FGCOLOR, SYSL_BGCOLOR, SYSL_ATTR);
help_attr = init_color_do(HELP_COLORID, HELP_FGCOLOR, HELP_BGCOLOR, HELP_ATTR);
info_attr = init_color_do(INFO_COLORID, INFO_FGCOLOR, INFO_BGCOLOR, INFO_ATTR);
file_attr = init_color_do(FILE_COLORID, FILE_FGCOLOR, FILE_BGCOLOR, FILE_ATTR);
viff_attr = init_color_do(VIFF_COLORID, VIFF_FGCOLOR, VIFF_BGCOLOR, VIFF_ATTR);
edit_attr = init_color_do(EDIT_COLORID, EDIT_FGCOLOR, EDIT_BGCOLOR, EDIT_ATTR);
} else {
viff_attr = A_REVERSE;
edit_attr = A_REVERSE;
}
status_attr = viff_attr;
(void)raw();
(void)noecho();
(void)keypad(stdscr, TRUE);
if (COLS > MAXFRAMEWIDTH) {
panic("cannot display %d columns; max is %d", COLS, MAXFRAMEWIDTH);
}
}
static void
cleanup(void)
{
free_difflines();
free(linetbl);
free(options);
free(diffcmd);
}
static void
cleanup_curses(void)
{
(void)move(VIEWLINES, 0);
(void)clrtoeol();
(void)refresh();
(void)endwin();
setbuf(stdout, NULL);
curses_on = FALSE;
}
static void
free_difflines(void)
{
while (0 < nline) {
nline--;
if (!SYSLINE(linetbl+nline)) free(linetbl[nline].txt);
}
}
static void
viff_statusline(void)
{
char diffinfo[40];
char focus[30];
if (diffend-diffbeg < VIEWLINES) {
diffinfo[0] = '\0';
} else {
sprintf(diffinfo, " (%d+%d)", diffmid-diffbeg-1, diffend-diffmid-1);
}
if (Focus == FOCUS_BOTH) {
focus[0] = '\0';
} else {
int n = Focus==FOCUS_FILE1 ? sel1 : ndiff-sel1;
char dir =Focus==FOCUS_FILE1 ? '<' : '>';
sprintf(focus, " %cnavigate %d diff%s%c", dir, n, n==1?"":"s", dir);
}
fstatusline("Viff", "%d/%d %d-%d%s%s",
currdiff, ndiff, sel1, ndiff-sel1, diffinfo, focus);
if (topln <= diffmid && diffmid < topln+VIEWLINES) {
(void)move(diffmid-topln, 0);
} else {
(void)move(VIEWLINES, 0);
}
}
void
edit_statusline(LINE *line, int memx, int winx)
{
char colbuf[20], linebuf[40];
char chbuf[50];
chbuf[0] = '\0';
if (SYSLINE(line)) {
strcpy(colbuf, "- "); strcpy(linebuf, "-");
} else {
sprintf(colbuf, "%-2d", winx+1);
if (line->new) {
strcpy(linebuf, "new");
} else {
char *p = linebuf;
if (line->broken) *p++ = '*';
if (line->line1==0) *p++ = '-';
else p+=sprintf(p, "%u", line->line1);
if (line->line1 == 0 || line->line1 != line->line2) {
*p++ = '/';
if (line->line2==0) *p++ = '-';
else p+=sprintf(p, "%d", line->line2);
}
*p = '\0';
}
if (memx < line->len) {
unsigned char ch = line->txt[memx];
if (ISCTRL(ch) || ch >= 0x80) {
sprintf(chbuf, " char= %#x %u %#o", ch, ch, ch);
if (ch < 0x20 && isupper(ch+0x40)) {
sprintf(chbuf+strlen(chbuf), " ^%c", ch+0x40);
}
}
}
}
fstatusline("Edit", "col:%s lin:%s%s", colbuf, linebuf, chbuf);
}
static void
build_text(char *buf, char left, char ch, char right, char *fname)
{
char cont[10] = {0};
memset(buf, ch, COLS);
if ((int)strlen(fname) > COLS/2) {
#if defined(MSDOS) || defined(WIN32)
if (isalpha(((unsigned char*)fname)[0]) && fname[1] == ':') {
memcpy(cont, fname, fname[2]==SEPCHAR ? 3 : 2);
}
#endif
strcat(cont, CONTSTR);
fname += strlen(cont)+strlen(fname)-COLS/2;
}
buf[COLS/4+sprintf(buf+COLS/4, "%c%s%s%c",
FRAME_NAMELEFT, cont, fname, FRAME_NAMERIGHT)] = ch;
buf[0] = left;
buf[COLS-1] = right;
}
static void
build_frame_texts(void)
{
memset(midtext, FRAME_MIDCH, COLS);
midtext[0] = FRAME_MIDLEFT;
midtext[COLS-1] = FRAME_MIDRIGHT;
build_text(toptext, FRAME_TOPLEFT, FRAME_TOPCH, FRAME_TOPRIGHT, Fname1);
build_text(bottext, FRAME_BOTLEFT, FRAME_BOTCH, FRAME_BOTRIGHT, Fname2);
}
static void
skip_lines(FILE *fp, int n)
{
while (n-- > 0) {
while (fgetc(fp) != '\n') {
if (feof(fp)) break;
}
}
}
static void
buffer_text_line(LINETYPE type, BOOLEAN incl, char *text, int line1, int line2)
{
LINE *line;
if (report) return; /* do nothing */
line = buffer_line(type, incl);
line->len = strlen(text);
if (0 < line->len && text[line->len-1] == '\n') {
line->len--; /* get rid of trailing '\n' */
}
line->room = 0;
line->txt = memcpy(xmalloc(line->len), text, line->len);
line->line1 = line1;
line->line2 = line2;
line->new = FALSE;
line->broken = FALSE;
}
static LINE *
buffer_line(LINETYPE type, BOOLEAN incl)
{
LINE *line;
if (report) return NULL;
if (nline == linetblsiz) { /* enlarge line table */
#ifdef MSDOS
#define MAX_LINES (unsigned)(0xff80/sizeof(*line))
if (linetblsiz == MAX_LINES) { /* cannot enlarge table */
panic("too many lines in file; max %u", MAX_LINES);
} else if (linetblsiz+LINETABINC > MAX_LINES) { /* make it maxsize */
linetblsiz = MAX_LINES; /* next enlargement will panic() */
} else
#endif
linetblsiz += LINETABINC; /* and do it in chunks */
linetbl = xrealloc(linetbl, sizeof(*line)*linetblsiz);
}
line = linetbl + nline++; /* shorthand alias */
line->type = type;
line->incl = incl;
return line;
}
void
set_topline(int ln)
{
int beg, end;
if (ln == topln) return;
(void)move(0, 0);
if (topln < ln && ln < topln+VIEWLINES) {
beg = topln+VIEWLINES; end = ln+VIEWLINES;
do {
deleteln();
} while (++topln < ln);
} else if (topln-VIEWLINES < ln && ln < topln) {
beg = ln; end = topln;
do {
insertln();
} while (ln < --topln);
} else {
topln = ln;
beg = topln;
end = topln+VIEWLINES;
}
for (ln = beg; ln < end; ln++) {
putline(ln);
}
viff_statusline();
}
static void
putline_txt(LINE *line)
{
int memx, winx;
char *p, *pws, *pend, *pbeg;
unsigned char space, tab;
pbeg = NULL;
for (winx=0, memx=0, p=line->txt; memx < line->len; memx++, p++) {
if (pbeg == NULL && winx >= Scrollx) {
pbeg = winx>Scrollx ? p-1 : p; /* > if there's a tab; go back */
}
if (winx >= Scrollx+COLS) break;
if (*p == '\t') {
winx += tabwidth-winx%tabwidth;
} else {
winx++;
}
}
if (pbeg == NULL) { /* no visible text */
(void)clrtoeol();
return;
}
/* p now points just beyond text to print and pbeg points to its start */
/* make pws point to last non-whitespace character */
pws = pend = p;
if (memx == line->len) {
for (; pbeg < pws; pws--) {
if (!isspace(((unsigned char*)pws)[-1])) break;
}
}
space = tab = ' ';
for (winx=0, p=pbeg; p < pend; p++) {
if (winx == COLS-1 && p+1-line->txt < line->len) {
(void)attron(cont_attr);
ADDRAWCH(CONT_CHAR); winx++;
break;
}
if (p == pws) {
space = SPACE_CHAR;
tab = TAB_CHAR;
(void)attron(space_attr);
}
switch (*p) {
case '\t':
do {
if (winx == COLS-1 &&
((winx+1+Scrollx) % tabwidth || p+1-line->txt < line->len)) {
(void)attron(cont_attr);
ADDRAWCH(CONT_CHAR); winx++;
break;
}
ADDRAWCH(tab); winx++;
} while ((winx+Scrollx) % tabwidth);
break;
case ' ':
ADDRAWCH(space); winx++;
break;
default:
if (ISCTRL(*(unsigned char*)p)) {
(void)attron(ctrlch_attr);
ADDRAWCH(CTRL_CHAR);
(void)attroff(ctrlch_attr);
} else {
ADDRAWCH(*(unsigned char*)p);
}
winx++;
break;
}
if (winx == COLS) break;
}
if (winx < COLS) {
(void)clrtoeol();
}
}
void
putline(int ln)
{
LINE *line = linetbl + ln;
if (ln < topln || topln+VIEWLINES <= ln) return;
if (nline <= ln) {
(void)move(ln-topln, 0);
(void)clrtoeol();
} else if (!SYSLINE(line)) {
(void)attron(line->type == COMMON ? ATTR_COMMON :
line->incl ? ATTR_INCL :
ATTR_EXCL);
(void)move(ln-topln, 0);
putline_txt(line);
(void)standend();
} else {
char *text, *p;
chtype attr = 0;
switch (line->type) {
case TOPSEP: text = toptext; break;
case MIDSEP: text = midtext; break;
case BOTSEP: text = bottext; break;
}
(void)attron(sysl_attr|attr);
(void)move(ln-topln, 0);
for (p = text; *p; p++) {
ADDRAWCH(*(unsigned char*)p);
}
(void)standend();
if (COLS < p-text) {
(void)clrtoeol();
}
}
}
void
goto_curr_diff(BOOLEAN center)
{
int diffsight;
int lines;
int ln;
if (!center &&
diffbeg >= topln && diffend < topln+VIEWLINES) return; /* all visible */
/* I want to position the diff the absolute best way possible!
If diff can't be viewed entirely then
if both beginning and middle can be viewed, set beginning at top
else if both middle and bottom can be viewed, set end at bottom
else center middle of diff on the screen
If diff can be viewed entirely then
if beginning is visible, scroll so the end is at the bottom
else if end is visible, scroll so beginning is at the top
else center the entire diff on the screen
Also, if possible to show DIFFSIGHT lines to both sides of the diff
*/
lines = diffend - diffbeg + 1;
if (lines > VIEWLINES) {
if (diffmid-diffmid+1 <= VIEWLINES) {
ln = diffbeg;
} else if (diffend-diffmid+1 <= VIEWLINES) {
ln = diffend - (VIEWLINES-1);
} else {
ln = diffmid - (int)(VIEWLINES/2);
}
} else if (center) {
ln = diffbeg - (int)((VIEWLINES-lines)/2);
} else {
diffsight = MIN(DIFFSIGHT, VIEWLINES-lines);
if (diffbeg >= topln && diffbeg < topln+VIEWLINES) {
ln = diffend+diffsight - (VIEWLINES-1);
} else if (diffend >= topln && diffend < topln+VIEWLINES) {
ln = diffbeg-diffsight;
} else {
ln = diffbeg - (int)((VIEWLINES-lines)/2);
}
}
if (nline-VIEWLINES < ln) ln = nline-VIEWLINES;
if (ln < 0) ln = 0;
set_topline(ln);
viff_statusline();
}
static void
goto_diff(int diff)
{
int ln, n, curr;
for (n=diff, ln=0, curr=0; ln < nline; ln++) {
if (linetbl[ln].type == MIDSEP) {
curr++;
if (Focus == FOCUS_BOTH && --n==0) break;
if (Focus == FOCUS_FILE1 && linetbl[ln-1].incl && --n==0) break;
if (Focus == FOCUS_FILE2 && linetbl[ln+1].incl && --n==0) break;
}
}
if (ln == nline) return; /* no such diff */
set_curr_diff(ln, curr);
}
static void
set_curr_diff(int midln, int diff)
{
diffmid = midln;
for (diffend = midln+1; linetbl[diffend].type != BOTSEP; diffend++)
;
for (diffbeg = midln-1; linetbl[diffbeg].type != TOPSEP; diffbeg--)
;
currdiff = diff;
goto_curr_diff(FALSE);
}
static void
goto_first_diff(void)
{
goto_diff(1);
}
static void
goto_last_diff(void)
{
goto_diff(Focus == FOCUS_BOTH ? ndiff :
Focus == FOCUS_FILE1 ? sel1 : ndiff-sel1);
}
static BOOLEAN
goto_next_diff()
{
int ln, curr;
for (ln=diffend+1, curr=currdiff; ln < nline; ln++) {
if (linetbl[ln].type == MIDSEP) {
curr++;
if (Focus == FOCUS_BOTH) break;
if (Focus == FOCUS_FILE1 && linetbl[ln-1].incl) break;
if (Focus == FOCUS_FILE2 && linetbl[ln+1].incl) break;
}
}
if (ln == nline) return FALSE; /* no more diffs ahead of us */
set_curr_diff(ln, curr);
return TRUE;
}
static BOOLEAN
goto_prev_diff()
{
int ln, curr;
for (ln=diffbeg-1, curr=currdiff; ln >= 0; ln--) {
if (linetbl[ln].type == MIDSEP) {
curr--;
if (Focus == FOCUS_BOTH) break;
if (Focus == FOCUS_FILE1 && linetbl[ln-1].incl) break;
if (Focus == FOCUS_FILE2 && linetbl[ln+1].incl) break;
}
}
if (ln < 0) return FALSE; /* no more diffs before this point */
set_curr_diff(ln, curr);
return TRUE;
}
void
goto_nearest_diff(int ln)
{
int i, prev, next;
int diff;
LINE *line;
switch (linetbl[ln].type) {
case DIFF: /* in diff; look for mid or bottom */
while (linetbl[++ln].type == DIFF)
;
break;
case COMMON: /* outside diff; pick nearest */
for (prev = ln-1; prev >= 0; prev--)
if (linetbl[prev].type == BOTSEP) break;
for (next = ln+1; next < nline; next++)
if (linetbl[next].type == TOPSEP) break;
if (prev < 0) ln = next;
else if (next == nline) ln = prev;
else ln = ln-prev < next-ln ? prev : next;
break;
}
/* now we're at top, bottom, or middle of a diff */
switch (linetbl[ln].type) {
case TOPSEP: /* at top; look for middle */
while (linetbl[++ln].type != MIDSEP)
;
break;
case MIDSEP: /* already at middle of diff */
break;
case BOTSEP: /* at bottom; look for middle */
while (linetbl[--ln].type != MIDSEP)
;
break;
}
/* find out what number diff this is */
for (i=0, line=linetbl, diff=0; i <= ln; i++, line++) {
if (line->type == MIDSEP) diff++;
}
set_curr_diff(ln, diff);
}
static void
collapse_diff(void)
{
int i, n;
if (linetbl[diffbeg].incl) {
LINE tmp;
if (diffmid == diffend-1) return; /* nothing to move */
/* The lower diff is moved up beneath the upper diff:
A A
B B
C -> C
- D
D E
E -
*/
tmp = linetbl[diffmid];
for (i=diffmid, diffmid=diffend-1; i <= diffmid; i++) {
linetbl[i] = i==diffmid ? tmp : linetbl[i+1];
linetbl[i].incl = TRUE;
putline(i);
}
} else {
LINE *tmp;
if (diffmid == diffbeg+1) return; /* nothing to move */
/* The upper diff is moved beneath the lower diff:
A -
B D
C -> E
- A \
D B > these (-DE) are remembered in tmp
E C /
*/
n = diffend-diffmid;
tmp = malloc(n * sizeof(*tmp));
memcpy(tmp, linetbl+diffmid, n * sizeof(*tmp));
for (i = diffmid-1; i > diffbeg; i--) {
linetbl[i+n] = linetbl[i];
linetbl[i+n].incl = TRUE;
putline(i+n);
}
diffmid = diffbeg+1;
for (i = 0; i < n; i++) {
linetbl[diffmid+i] = tmp[i];
putline(diffmid+i);
}
free(tmp);
}
modified = TRUE;
goto_curr_diff(FALSE);
(void)refresh();
}
static void
choose_diff(BOOLEAN diff1)
{
int ln;
if ( diff1 && linetbl[diffbeg].incl ||
!diff1 && !linetbl[diffbeg].incl) return; /* no change in diff */
if (diff1) sel1++; /* change from file1 to 2 */
else sel1--; /* vice versa */
for (ln = diffbeg; ln < diffmid; ln++) {
linetbl[ln].incl = diff1;
putline(ln);
}
for (ln = diffend; diffmid < ln; ln--) {
linetbl[ln].incl = !diff1;
putline(ln);
}
(void)refresh();
}
static void
choose_all_diff(BOOLEAN diff1)
{
BOOLEAN incl;
LINE *line;
int ln;
incl = diff1;
for (ln=0, line=linetbl; ln < nline; ln++, line++) {
if (line->type == COMMON) continue;
line->incl = incl;
if (line->type == MIDSEP || line->type == BOTSEP) {
incl = !incl;
}
putline(ln); /* non-visible lines are ignored */
}
sel1 = diff1 ? ndiff : 0;
(void)refresh();
}
static char *
read_num(char *str, int *val)
{
int v;
for (v = 0; isdigit(*str); str++) {
v = v*10 + *str -'0';
}
*val = v;
return str;
}
static BOOLEAN
read_edit_cmd(FILE *fp, EDITCMD *cmd, BOOLEAN *errmsg)
{
char buf[150], *p;
int skip;
do {
if (fgets(buf, sizeof(buf), fp) == NULL) return FALSE;
p = buf;
p = read_num(p, &cmd->beg1);
if (*p==',') p = read_num(p+1, &cmd->end1);
else cmd->end1 = cmd->beg1;
cmd->cmd = *p++;
p = read_num(p, &cmd->beg2);
if (*p==',') p = read_num(p+1, &cmd->end2);
else cmd->end2 = cmd->beg2;
switch (cmd->cmd) {
case 'a': skip = cmd->end2-cmd->beg2+1; break;
case 'c': skip = (cmd->end1-cmd->beg1+1)
+ (cmd->end2-cmd->beg2+1) + 1; break;
case 'd': skip = cmd->end1-cmd->beg1+1; break;
default:
if (buf[0]) {
buf[sizeof(buf)-1] = '\0'; /* make sure it's 0-terminated */
for (p = buf; *p; p++) {
if (!isprint(*(unsigned char*)p)) break;
}
*p = '\0';
message("diff says: \"%s\"", buf);
} else {
message("diff produced no output");
return FALSE; /* probably bogus diff-file */
}
*errmsg = TRUE;
skip = -1;
}
} while (skip < 0);
skip_lines(fp, skip);
return TRUE;
}
static void
set_tab_width(void)
{
char buf[200];
int val;
int ln;
buf[0] = '\0';
for (;;) {
if (!input(INPUT_NUMERIC, buf, "set tabwidth (now %d):", tabwidth)) return;
val = atoi(buf);
if (val > 0 && val <= MAXTABWIDTH) break;
message("tabwidth must be between 1 and %d", MAXTABWIDTH);
}
tabwidth = val;
for (ln = topln; ln < topln+VIEWLINES; ln++) {
putline(ln);
}
(void)refresh();
}
static void
set_focus()
{
int ln;
switch (Focus) {
case FOCUS_BOTH: Focus = FOCUS_FILE1; break;
case FOCUS_FILE1: Focus = FOCUS_FILE2; break;
case FOCUS_FILE2: Focus = FOCUS_BOTH; break;
}
for (ln = topln; ln < topln+VIEWLINES; ln++) {
putline(ln);
}
(void)refresh();
}
static BOOLEAN
write_file(char *fname, char *eol, int flags)
{
char tmpfname[FILENAME_MAX+20], *p;
struct stat st;
FILE *fp;
int ln;
char *text;
int i, len, eol_len;
BOOLEAN error;
if (flags & EXISTING_FILE) {
/* Write viffed output to a temp file and rename() that if the
* write succeeds. We can't use tmpnam() because files can't be
* rename'd across mounted file systems and the TMP dir might be
* on another disk.
* The stat()+fopen() below really should be an atomic operation,
* but the risk of another viff testing and choosing exactly the
* same name at the same time is rather theoretical.
*/
strcpy(tmpfname, fname);
p = strip_path(tmpfname);
for (fp=NULL, i=0; i < 20; i++) { /* try max n temp names */
sprintf(p, "viff%d.tmp", i);
if (stat(tmpfname, &st) == 0) continue; /* name exists; try another */
if ((fp=fopen(tmpfname, "wb")) != NULL) break;
}
if (stat(fname, &st) != 0) st.st_mode = 0;
} else {
fp = fopen(fname, "wb");
}
if (fp == NULL) {
#ifdef UNIX
if (access(".", W_OK) != 0) {
char buf[1024], *cwd = getcwd(buf, sizeof(buf));
if (!cwd) cwd = ".";
message("cannot write to directory \"%s\"", cwd);
} else
#endif
message("cannot write to \"%s\": %s", fname, my_strerror());
return FALSE;
}
statusline(0, "saving file %s ...", fname); refresh();
clearerr(fp); errno = 0;
if (flags & WRITE_ALL) {
ffile_info(fp);
}
eol_len = strlen(eol);
for (ln = 0; ln < nline; ln++) {
if (flags & WRITE_ALL) {
switch (linetbl[ln].type) {
case COMMON: /* NOBREAK */
case DIFF: text = linetbl[ln].txt; len = linetbl[ln].len; break;
case TOPSEP: text = toptext; len = sizeof(toptext)-1; break;
case MIDSEP: text = midtext; len = sizeof(midtext)-1; break;
case BOTSEP: text = bottext; len = sizeof(bottext)-1; break;
}
} else if (linetbl[ln].incl && !SYSLINE(linetbl+ln)) {
text = linetbl[ln].txt; len = linetbl[ln].len;
} else continue; /* unselected or system line */
fwrite(text, 1, len, fp);
fwrite(eol, 1, eol_len, fp);
}
error = ferror(fp);
fclose(fp);
if (error) {
message("error writing to \"%s\": %s", fname, my_strerror());
if ((flags & EXISTING_FILE) && remove(tmpfname)) {
message("could not remove temporary file \"%s\": %s",
tmpfname, my_strerror());
}
return FALSE;
}
if (flags & EXISTING_FILE) {
if (remove(fname)) { /* could not remove the old file */
struct stat st;
if (stat(fname, &st) == 0) { /* file exist, but remove failed */
message("cannot remove existing file \"%s\": %s",
fname, my_strerror());
if (remove(tmpfname)) { /* aik! can't remove our tmpfile! */
message("could not remove temporary file \"%s\": %s",
tmpfname, my_strerror());
}
return FALSE;
}
}
if (rename(tmpfname, fname)) { /* could not rename to real name */
message("rename failed: %s; file saved as \"%s\"!",
my_strerror(), tmpfname);
return FALSE;
}
#ifdef UNIX
if (st.st_mode != 0) {
/* don't bother checking for success; this is a favor, not mandatory */
chown(fname, st.st_uid, st.st_gid);
chmod(fname, st.st_mode);
}
#endif
}
viff_statusline();
return TRUE;
}
static int
user_commands(void)
{
int ch;
for (;;) {
viff_statusline();
(void)refresh();
ch = getch();
switch (ch) {
case KEY_IC:
case KEYCTRL('O'):
edit_mode();
break;
case KEY_HOME:
case KEYCTRL('A'):
#ifdef KEY_A1
case KEY_A1:
#endif
goto_first_diff();
break;
case KEY_END:
case KEYCTRL('E'):
goto_last_diff();
break;
case KEY_UP:
case KEYCTRL('P'):
#ifdef KEY_A2
case KEY_A2:
#endif
goto_prev_diff();
break;
case KEY_DOWN:
case KEYCTRL('N'):
#ifdef KEY_C2
case KEY_C2:
#endif
goto_next_diff();
break;
case KEY_LEFT:
case KEYCTRL('B'):
#ifdef KEY_B1
case KEY_B1:
#endif
choose_diff(TRUE);
break;
case KEY_RIGHT:
case KEYCTRL('F'):
#ifdef KEY_B3
case KEY_B3:
#endif
choose_diff(FALSE);
break;
case '.':
#ifdef KEY_B2
case KEY_B2:
#endif
goto_curr_diff(TRUE);
break;
case ' ':
choose_diff(!linetbl[diffbeg].incl);
break;
case '(':
choose_all_diff(TRUE);
break;
case ')':
choose_all_diff(FALSE);
break;
case KEY_PPAGE:
#ifdef ALT_V
case ALT_V:
#endif
#ifdef KEY_A3
case KEY_A3:
#endif
goto_prev_page();
break;
case KEY_NPAGE:
case KEYCTRL('V'):
#ifdef KEY_C3
case KEY_C3:
#endif
goto_next_page();
break;
case KEYCTRL('L'):
goto_nearest_diff(topln+VIEWLINES/2);
break;
#ifdef KEY_SHELP
case KEY_SHELP:
#endif
#ifdef KEY_LHELP
case KEY_LHELP:
#endif
case 'h':
case 'H':
case KEY_F(1):
help(viffmode_help, ARRAYSIZE(viffmode_help));
break;
case KEYCTRL('S'):
case KEYCTRL('R'):
search(ch);
break;
#ifdef UNIX
case 'r':
case 'R':
case KEY_REFRESH:
(void)wrefresh(curscr);
break;
#endif
case '\t':
set_focus();
break;
case 't':
case 'T':
set_tab_width();
break;
case 'i':
case 'I':
file_info();
break;
case '+':
collapse_diff();
break;
case '1':
if (write_file(Fname1, Eol1, EXISTING_FILE)) return ch;
break;
case '2':
if (write_file(Fname2, Eol2, EXISTING_FILE)) return ch;
break;
case '3':
if (write_file(Fname1, Eol1, EXISTING_FILE) &&
write_file(Fname2, Eol2, EXISTING_FILE)) return ch;
break;
case '4':
case '5':
{ char buf[300];
if (!input_filename(buf, ch)) break;
if (write_file(buf, EOL, ch=='5'?WRITE_ALL:0) && ch=='4') {
modified = FALSE;
}
}
break;
case '<':
case '>':
case 'q':
case 'Q':
case KEY_ESCAPE:
if (!confirm_quit()) break;
if (ch == 'Q') exit(EXIT_SUCCESS);
return ch;
}
}
}
static int
first_diff_to_show(void)
{
int i, ln;
if (ndiff == 0) return 0;
if (!ignoreheaders) return 1;
for (ln = 0; ln < nline; ln++) {
if (linetbl[ln].type == TOPSEP) break;
}
if (ln+4 < nline &&
linetbl[ln+1].type == DIFF &&
linetbl[ln+2].type == MIDSEP &&
linetbl[ln+3].type == DIFF &&
linetbl[ln+4].type == BOTSEP) {
for (i = 0; header_table[i]; i++) {
if (is_header_line(linetbl+ln+1, header_table[i]) &&
is_header_line(linetbl+ln+3, header_table[i])) {
return ndiff==1 ? 0 : 2;
}
}
}
return 1;
}
static BOOLEAN
is_header_line(LINE *line, char *header)
{
int headerlen = strlen(header);
int maxpos = line->len - headerlen;
int i;
for (i = 0; i < maxpos; i++) {
if (strncmp(line->txt+i, header, headerlen) == 0) return TRUE;
}
return FALSE;
}
static FILE *
open_viff_file(char *fname, char **eol, int *chp)
{
FILE *fp;
/* Recognize DOS or Unix end-of-line:
* DOS: CR+LF (\x0d\x0a)
* Unix: LF (\x0a)
*/
*eol = EOL;
if ((fp=fopen(fname, "rb")) != NULL) {
int ch;
while ((ch=getc(fp)) != EOF) {
if (ch == '\x0d') {
if (getc(fp) == '\x0a') { /* DOS */
*eol = DOS_EOL;
break;
}
} else if (ch == '\x0a') { /* Unix */
*eol = UNIX_EOL;
break;
}
}
fclose(fp);
}
if ((fp=fopen(fname, "r")) == NULL && verbose) {
*chp = message("could not open %s: %s", fname, my_strerror());
}
return fp;
}
static BOOLEAN
viff_files(int *chp)
{
char buf[MAXLINELEN], *p;
FILE *fpdiff;
EDITCMD cmd;
FILE *fp1, *fp2;
int line1, line2;
int i, skip, firstdiff;
BOOLEAN errmsg;
if ((fp1=open_viff_file(Fname1, &Eol1, chp)) == NULL) return FALSE;
if ((fp2=open_viff_file(Fname2, &Eol2, chp)) == NULL) {
fclose(fp1);
return FALSE;
}
/* Check that the files are in fact not the same file (eg through a link)
*/
#ifdef UNIX /* Under DOS, st_ino might not have a value (ie always be 0) */
{ struct stat stat1, stat2;
if (stat(Fname1, &stat1)==0 && stat(Fname2, &stat2)==0 &&
stat1.st_dev==stat2.st_dev && stat1.st_ino==stat2.st_ino) {
*chp = message("%s and %s is the same file", Fname1, Fname2);
fclose(fp1);
fclose(fp2);
return FALSE;
}
}
#endif
if (!report) {
statusline(0, "diffing %s and %s ...", Fname1, Fname2); refresh();
}
sprintf(buf, "%s %s %s %s", diffcmd, options, Fname1, Fname2);
fpdiff = popen(buf, "r");
if (fpdiff == NULL) {
fclose(fp1); fclose(fp2);
*chp = message("error running diff: %s", my_strerror());
return FALSE;
}
#if 0
/* I'm taking a chance here. Traditionally 0 means no error and not-0
* means error. However, it seems that some (most?) diff programs return
* 0 for "no differences" and 1(!) for "differences". Therefore I choose
* only to interpret error codes above 1 as real errors.
*/
if (errcode > 1) {
char buf[200];
*chp = message("error viffing %s and %s\n", Fname1, Fname2);
while (fgets(buf, sizeof buf, fpdiff)) {
*chp = message("diff complains: \"%s\"", buf);
}
return FALSE;
}
#endif
if (!report) {
statusline(0, "viffing %s and %s ...", Fname1, Fname2); refresh();
}
line1 = line2 = 1;
diffbeg = diffmid = diffend = -1;
Focus = FOCUS_BOTH;
errmsg = FALSE;
for (ndiff = sel1 = 0; read_edit_cmd(fpdiff, &cmd, &errmsg); ndiff++, sel1++) {
skip = cmd.beg1 - line1; /* the next skip lines are identical */
if (cmd.cmd == 'a') skip++;
for (i = 0; i < skip; i++, line1++, line2++) { /* read them from file1 */
fgets(buf, sizeof(buf), fp1);
buffer_text_line(COMMON, TRUE, buf, line1, line2);
}
skip_lines(fp2, skip); /* these are the same; just skip it */
if (diffbeg == -1) diffbeg = nline;
(void)buffer_line(TOPSEP, TRUE);
if (cmd.cmd=='c' || cmd.cmd=='d') { /* something new in file1 */
for (; line1 <= cmd.end1; line1++) {
fgets(buf, sizeof(buf), fp1);
buffer_text_line(DIFF, TRUE, buf, line1, 0);
}
}
if (diffmid == -1) diffmid = nline;
(void)buffer_line(MIDSEP, TRUE);
if (cmd.cmd=='c' || cmd.cmd=='a') { /* something new in file2 */
for (; line2 <= cmd.end2; line2++) {
fgets(buf, sizeof(buf), fp2);
buffer_text_line(DIFF, FALSE, buf, 0, line2);
}
}
if (diffend == -1) diffend = nline;
(void)buffer_line(BOTSEP, FALSE);
}
if (0 < ndiff) { /* remaining lines are identical */
while (fgets(buf, sizeof(buf), fp1) != NULL) {
buffer_text_line(COMMON, TRUE, buf, line1++, line2++);
}
}
fclose(fp1);
fclose(fp2);
pclose(fpdiff);
if (ndiff == 0 && errmsg)
return FALSE;
if (report) { /* reports are done now */
if (brief_report) {
if (ndiff > 0) printf("%s\n", Fname1);
} else if (verbose || ndiff > 0) {
printf("%3d diff%s between %s and %s\n",
ndiff, ndiff==1?" ":"s", Fname1, Fname2);
Ndiff += ndiff;
Ndiffrep++;
}
return TRUE; /* don't-care; reports don't "go back" */
}
firstdiff = first_diff_to_show();
if (firstdiff == 0) {
free_difflines();
if (verbose) {
if (ndiff == 0) {
*chp = message("files %s and %s are identical", Fname1, Fname2);
} else {
*chp = message("files %s and %s have a header difference",
Fname1, Fname2);
}
}
return FALSE;
}
if (strcmp(p=strip_path(Fname1), strip_path(Fname2)) == 0) {
strncpy(viffname, p, VIFFNAME_LEN)[VIFFNAME_LEN-1] = '\0';
} else {
viffname[0] = '\0';
}
build_frame_texts();
(void)clear();
topln = -VIEWLINES; /* force a redraw instead of scroll */
goto_diff(firstdiff);
*chp = user_commands();
modified = FALSE;
sel1 = ndiff = 0;
free_difflines();
return TRUE;
}
static void
check_for_stop(void)
{
int ch;
if (report) return;
if (nodelay(stdscr, TRUE) == ERR) return;
ch = getch();
(void)nodelay(stdscr, FALSE);
if (ch == 'Q' ||
(ch==KEY_ESCAPE || ch=='q') && ask(stop_help, ARRAYSIZE(stop_help),
"yn", 'n', "quit viff?")=='y') {
exit(EXIT_SUCCESS);
}
}
int
main(int argc, char *argv[])
{
char path[FILENAME_MAX], *pathend;
char *diff1, *diffN;
char *skipname = NULL;
BOOLEAN skipgenerated = FALSE;
BOOLEAN isdir1, isdirN;
int ch, i;
if (argc == 2 && strcmp(argv[1], "vaff") == 0) { /* #include <stdjoke.h> */
printf("vuff!\n");
exit(EXIT_SUCCESS);
}
signal(SIGINT, SIG_IGN);
options = xstrdup("");
atexit(cleanup);
while (--argc > 0 && (++argv)[0][0] == '-') {
char *op = argv[0];
if (op[1] == '\0') error("missing option letter");
while (*++op) {
switch (*op) {
case 't':
if (op[1] || --argc == 0) {
error("missing tabsize argument for -t");
}
tabwidth = atoi(*++argv);
if (tabwidth < 1 || tabwidth > MAXTABWIDTH) {
error("tabwidth must be between 1 and %d", MAXTABWIDTH);
}
break;
case 'o':
if (op[1] || --argc == 0) {
error("missing option argument for -o");
} else {
int len = strlen(options);
++argv;
options = (char*)xrealloc(options, len+1+strlen(*argv)+1);
sprintf(options+len, " %s", *argv);
}
break;
case 'f':
if (op[1] || --argc == 0) {
error("missing filename argument for -f");
}
skipname = lower_str(*++argv);
break;
case 'p':
if (op[1] || --argc == 0) {
error("missing program argument for -p");
}
diffcmd = xstrdup(lower_str(*++argv));
break;
case 'h':
case 'H':
version_info();
printf("\n");
extended_usage();
break;
case 'v': verbose = TRUE; break;
case 'L': brief_report = TRUE; /*NOBREAK*/
case 'l': report = TRUE; break;
case 'i': ignoreheaders = TRUE; break;
case 'x': skipgenerated = TRUE; break;
case 'm': monochrome = TRUE; break;
case 'V':
version_info();
return 0;
default:
error("unknown option '%.20s'\nuse viff -h for help", argv[0]);
}
}
}
if (argc < 2) brief_usage();
if (!diffcmd) diffcmd = xstrdup(DIFFCMD);
if (!find_diffcmd()) {
error("could not find %s", diffcmd);
}
diff1 = argv[0];
isdir1 = is_dir(diff1);
diffN = argv[argc-1];
isdirN = is_dir(diffN);
if (isdir1 && isdirN) {
if (argc > 2) error("both first and last argument is a directory");
construct_argv(diff1, diffN, &argc, &argv);
if (argc < 2) error("there are no files in %s", diff1);
diff1 = argv[0];
isdir1 = FALSE;
} else if (argc > 2 && !isdir1 && !isdirN) {
error("please specify a directory to viff these %d files against", argc);
}
init_curses();
init_edit_mode();
for (i = 0; i < argc; i++) { /* make sure DOS arguments are lower case */
lower_str(argv[i]);
}
/* If there are only two single arguments and one is a directory,
make viff behave as if two files had been specified, so it
will be verbose and not skip binaries, generated etc.
*/
if (argc == 2) {
verbose = TRUE;
if (isdir1 && !isdirN) {
strcpy(make_path(path, diff1), strip_path(diffN));
diff1 = path;
isdir1 = FALSE;
} else if (!isdir1 && isdirN) {
strcpy(make_path(path, diffN), strip_path(diff1));
diffN = path;
isdirN = FALSE;
}
}
if (!isdir1 && !isdirN) {
Fileno = Nfiles = 1;
Fname1 = diff1; Fname2 = diffN;
while (viff_files(&ch) && ch == '<')
;
} else {
int firstfile=1;
pathend = make_path(path, isdir1 ? diff1 : diffN);
Nfiles = argc-1;
if (isdirN) argv--;
skip_directories(firstfile, argc, argv);
skip_binary_files(firstfile, argc, argv);
if (skipgenerated) {
skip_generated_files(firstfile, argc, argv);
}
Fileno = firstfile;
if (skipname) {
BOOLEAN skipwithpath = strpbrk(skipname, "\\:/")!=NULL;
for (; Fileno < argc; Fileno++) {
strcpy(pathend, strip_path(argv[Fileno]));
if (skipwithpath) { /* a name with path must match */
if (strcmp(skipname, path) == 0 ||
strcmp(skipname, argv[Fileno]) == 0) break;
} else { /* a name without path must match */
if (strcmp(skipname, pathend) == 0) break;
}
}
}
while (Fileno < argc) {
check_for_stop();
if (Fileno < firstfile) {
ch = '\0'; Fileno++;
continue;
}
if (argv[Fileno]) {
strcpy(pathend, strip_path(argv[Fileno]));
Fname1 = isdir1 ? path : argv[Fileno];
Fname2 = isdir1 ? argv[Fileno] : path;
if (!viff_files(&ch)) {
argv[Fileno] = NULL;
}
}
if (ch=='<') Fileno--;
else Fileno++;
}
/* Only print summary for non-brief reports, and then only
if verbose or there was any diffs at all.
*/
if (report && !brief_report && (verbose || Ndiff > 0)) {
printf("---------\n%3d diff%s in %d file%s\n",
Ndiff, Ndiff==1?"":"s", Ndiffrep, Ndiffrep==1?"":"s");
}
}
return 0;
}